package com.symaster.common.config;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.symaster.common.thread.WatchThread;
import com.symaster.common.util.IOUtil;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author yinmiao
 * @version 2022-05-03 10:27
 */
public class JsonConfig {

    private Charset charset = StandardCharsets.UTF_8;
    private final String fileName;
    private Integer saveInterval;
    private JSONObject configJson;
    private boolean update = false;
    private final Object updateLock = new Object();

    public JsonConfig(String fileName) {
        this(fileName, null);
    }

    public JsonConfig(String fileName, Integer saveInterval) {
        this.fileName = fileName;
        this.saveInterval = saveInterval;
        new AutoSave(this).start();
    }

    public void setSaveInterval(Integer saveInterval) {
        this.saveInterval = saveInterval;
    }

    public void reload() {
        File file = new File(fileName);
        if (!file.isFile()) {
            configJson = new JSONObject();
            return;
        }
        FileInputStream fileInputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {
            fileInputStream = new FileInputStream(file);
            inputStreamReader = new InputStreamReader(fileInputStream, charset);
            bufferedReader = new BufferedReader(inputStreamReader);

            StringBuilder sb = new StringBuilder();
            Stream<String> lines = bufferedReader.lines();
            List<String> collect = lines.collect(Collectors.toList());
            for (String s : collect) {
                sb.append(s);
                sb.append("\n");
            }
            configJson = JSONObject.parseObject(sb.toString());
        } catch (FileNotFoundException e) {
            throw new RuntimeException(String.format("FileNotFoundException: %s", e.getMessage()));
        } finally {
            IOUtil.close(bufferedReader, inputStreamReader, fileInputStream);
        }
    }

    public void save() {
        if (saveInterval != null && saveInterval > 0) {
            return;
        }
        _save();
    }

    public void setCharset(Charset charset) {
        this.charset = charset;
    }

    public <T> List<T> getList(String key, Class<T> clazz) {
        if (configJson == null) {
            reload();
        }
        JSONArray jsonArray = configJson.getJSONArray(key);
        if (jsonArray == null || jsonArray.size() - 1 < 0) {
            return new ArrayList<>();
        }
        return jsonArray.toJavaList(clazz);
    }

    public List<String> getStringList(String key) {
        return getList(key, String.class);
    }

    public List<Boolean> getBooleanList(String key) {
        return getList(key, Boolean.class);
    }

    public List<Integer> getIntegerList(String key) {
        return getList(key, Integer.class);
    }

    public List<Double> getDoubleList(String key) {
        return getList(key, Double.class);
    }

    public Optional<String> getString(String key) {
        if (configJson == null) {
            reload();
        }
        return Optional.ofNullable(configJson.getString(key));
    }

    public Optional<Boolean> getBoolean(String key) {
        if (configJson == null) {
            reload();
        }
        return Optional.ofNullable(configJson.getBoolean(key));
    }

    public Optional<Integer> getInteger(String key) {
        if (configJson == null) {
            reload();
        }
        return Optional.ofNullable(configJson.getInteger(key));
    }

    public Optional<Double> getDouble(String key) {
        if (configJson == null) {
            reload();
        }
        return Optional.ofNullable(configJson.getDouble(key));
    }

    public <T> Optional<T> get(String key, Class<T> clazz) {
        if (configJson == null) {
            reload();
        }
        return Optional.ofNullable(configJson.getObject(key, clazz));
    }

    public Optional<Object> get(String key) {
        if (configJson == null) {
            reload();
        }
        return Optional.ofNullable(configJson.get(key));
    }

    public void put(String key, Object value) {
        synchronized (this.updateLock) {
            if (configJson == null) {
                reload();
            }
            configJson.put(key, value);
            update = true;
        }
    }

    private void _save() {
        if (configJson == null) {
            return;
        }


        String json = configJson.toJSONString();
        if (StringUtils.isEmpty(json)) {
            return;
        }
        File file = new File(fileName);
        File parentFile = file.getParentFile();
        if (parentFile != null) {
            if (!parentFile.isDirectory() && !parentFile.mkdirs()) {
                throw new RuntimeException(String.format("无法创建目录: %s", parentFile));
            }
        }

        File tempFile = getTempFile(parentFile);

        FileOutputStream fileOutputStream = null;
        OutputStreamWriter outputStreamWriter = null;
        try {
            fileOutputStream = new FileOutputStream(tempFile);
            outputStreamWriter = new OutputStreamWriter(fileOutputStream, charset);
            outputStreamWriter.write(json);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            IOUtil.close(outputStreamWriter, fileOutputStream);
        }
        boolean delete = file.delete();
        boolean b = tempFile.renameTo(file);
    }

    private File getTempFile(File file) {
        String path;
        if (file == null) {
            path = "";
        } else {
            path = file.getPath();
        }
        File file1 = new File(path + File.separator + "." + UUID.randomUUID());
        if (file1.isFile()) {
            return getTempFile(file);
        }
        return file1;
    }

    static class AutoSave extends WatchThread {

        private final JsonConfig jsonConfig;

        public AutoSave(JsonConfig jsonConfig) throws IllegalArgumentException {
            super(jsonConfig.saveInterval);
            this.jsonConfig = jsonConfig;
        }

        @Override
        protected void execute() throws Exception {
            synchronized (jsonConfig.updateLock) {
                if (jsonConfig.update) {
                    jsonConfig._save();
                    jsonConfig.update = false;
                }
            }
        }
    }
}
